/**
 * <p>Original Author: Daniel Freeman    http://www.danielfreeman.asia</p>
 *
 * <p>Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:</p>
 *
 * <p>The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.</p>
 *
 * <p>THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS' OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 * THE SOFTWARE.</p>
 *
 * <p>Licensed under The MIT License</p>
 * <p>Redistributions of files must retain the above copyright notice.</p>
 * <p>You are permitted to modify and enhance this code,</p>
 * <p>As a courtesy, please credit the original author, Daniel Freeman   http://www.danielfreeman.asia</p>
 */

// Procedural Generation Of Room Mesh

package
{
	[SWF(frameRate="60")]	
	
	import com.adobe.utils.*;
	
	import flash.display.Bitmap;
	import flash.display.Sprite;
	import flash.display.Stage3D;
	import flash.display.StageAlign;
	import flash.display.StageScaleMode;
	import flash.display3D.Context3D;
	import flash.display3D.Context3DProgramType;
	import flash.display3D.Context3DTextureFormat;
	import flash.display3D.Context3DTriangleFace;
	import flash.display3D.Context3DVertexBufferFormat;
	import flash.display3D.IndexBuffer3D;
	import flash.display3D.Program3D;
	import flash.display3D.VertexBuffer3D;
	import flash.display3D.textures.Texture;
	import flash.events.Event;
	import flash.geom.Matrix3D;
	import flash.geom.Vector3D;
	import flash.utils.ByteArray;
	
	
	public class RoomExperiment3 extends Sprite {
		
		[Embed( source = "textures/tex.jpg" )]
		protected const PANEL:Class;
		
		protected static const WIDTH:int = 800;
		protected static const HEIGHT:int = 480;

		protected static const COLOURS:Vector.<uint> = Vector.<uint>([0x333333, 0xCCCCCC, 0x111111, 0xFF9933, 0xFFFFFF]);  // Colour palette
		protected static const CORRIDOR_SEGMENT_WIDTH:Number = 0.5;
		protected static const PARTITION_WIDTH:Number = 0.05;
		
		protected static const CORRIDOR:Vector.<Number> = Vector.<Number>([-0.65,0, 0,  -0.5,0, 3,  -0.5,0.1, 3,   -0.6,0.2, 2,  -0.8,0.90, 1,  -0.20,0.85,2,  0.8,0.85,2,   0.8, 2.0, 4]);
		protected static const PARTITION:Vector.<Number> = Vector.<Number>([-0.5,0, 2,  -0.7,0.75, 1,  -0.5,0.80,1,   0.8,0.85, 0,  0.81,0.90, 0,   0.81, 4.0, 0 ]);

		protected static const LINKS:Vector.<int> = Vector.<int>([0, 4, 5, 6, 7]);
		
		protected static const CORRIDOR_ANGLE:Number = 10;
		protected static const CORRIDOR_LENGTH:int = 18;
		
		protected static const N:int = 9;
		
		protected var _indices:Vector.<uint> = new Vector.<uint>;
		protected var _reverseIndices:Vector.<uint> = new Vector.<uint>;
		
		protected var _vertices:Vector.<Number> = new Vector.<Number>;
		
		protected var _context3D:Context3D;
		protected var _shaderProgram:Program3D;
		protected var _vertexBuffer:VertexBuffer3D;
		protected var _indexBuffer:IndexBuffer3D;
		protected var _reverseIndexBuffer:IndexBuffer3D;
		protected var _modelMatrix:Matrix3D = new Matrix3D();
		protected var _rotationMatrix:Matrix3D;
		protected var _finalMatrix:Matrix3D = new Matrix3D();
		
		protected var _projectionMatrix:PerspectiveMatrix3D = new PerspectiveMatrix3D();
		
		protected var _t:Number = 0;
		
		protected var _fragmentShaderAssembler:ByteArray;
		protected var _vertexShaderAssembler:ByteArray;
		protected var _reflectionShaderAssembler:ByteArray;
		
		protected var _texture:Texture;
		
		public function RoomExperiment3(screen:Sprite = null) {
			if (screen)
				screen.addChild(this);
			
			stage.scaleMode = StageScaleMode.NO_SCALE;
			stage.align = StageAlign.TOP_LEFT;

			generateCorridor(CORRIDOR_LENGTH, CORRIDOR, PARTITION, CORRIDOR_SEGMENT_WIDTH, PARTITION_WIDTH, LINKS,CORRIDOR_ANGLE, true, true);
			
			stage.stage3Ds[0].addEventListener(Event.CONTEXT3D_CREATE, onContext3DCreate);
			stage.stage3Ds[0].requestContext3D();
		}
		
		
		protected function generateCorridor(n:int, segment:Vector.<Number>, divider:Vector.<Number>, segmentWidth:Number, dividerWidth:Number, links:Vector.<int>, rotate:Number = 0, shadow:Boolean = false, join:Boolean = false):void {
			var transform:Matrix3D = new Matrix3D();
			var transform2:Matrix3D = new Matrix3D();
			var x:Number;
			var y:Number;
			var colour:Vector3D;
			var vector0:Vector3D;
			var vector1:Vector3D;
			var i:int;
			
			for (var s:int = 0; s < n; s++) {
			
				var start0:int = _vertices.length/N;
				
				for (i = 0; i < divider.length; i+=3) {
					x = divider[i];
					y = divider[i+1];
					colour = toColourVector(COLOURS[divider[i+2]]);
					vector0 = transform.transformVector(new Vector3D(x, y, 0.0));
					_vertices.push(vector0.x, vector0.y, vector0.z, (s%2==0) ? 0.0 : 1.0, ((i/3+s)%2==0) ? 1.0 : 0.0, (s%2==0) ? 0.0 : 1.0, colour.x, colour.y, colour.z);
				}
				
				if (s > 0) {
					for (i = 0; i < links.length-1; i++) {
						_indices.push(start1+links[i]*2+1, start0+i, start0+i+1, start1+links[i]*2+1, start0+i+1, start1+links[i+1]*2+1);
						_reverseIndices.push(start0+i+1, start0+i, start1+links[i]*2+1, start1+links[i+1]*2+1, start0+i+1, start1+links[i]*2+1);

					}
				}
				transform.copyToMatrix3D(transform2);
				transform2.appendRotation(rotate, Vector3D.Y_AXIS);
				transform2.appendTranslation(0.0, 0.0, segmentWidth);
				transform2.appendRotation(rotate, Vector3D.Y_AXIS);
				
				var start1:int = _vertices.length/N;
				
				for (i = 0; i < segment.length; i+=3) {
					x = segment[i];
					y = segment[i+1];
					colour = toColourVector(COLOURS[segment[i+2]]);
					vector0 = transform.transformVector(new Vector3D(x, y, dividerWidth));
					vector1 = transform2.transformVector(new Vector3D(x, y, 0.0));
					_vertices.push(vector0.x, vector0.y, vector0.z, (s%2==0) ? 0.25 : 0.75, ((i/3+s)%2==0) ? 0.0 : 1.0, 0.0, colour.x, colour.y, colour.z);
					_vertices.push(vector1.x, vector1.y, vector1.z, (s%2==0) ? 0.75 : 0.25, ((i/3+s)%2==0) ? 0.0 : 1.0, shadow ? 2.0 : 1.0, colour.x, colour.y, colour.z);
				}
				
				for (i = 0; i < links.length-1; i++) {
					_indices.push(start0+i, start1+links[i]*2, start1+links[i+1]*2, start0+i, start1+links[i+1]*2, start0+i+1);
					_reverseIndices.push(start1+links[i+1]*2, start1+links[i]*2, start0+i, start0+i+1, start1+links[i+1]*2, start0+i);

				}
				
				for (i = start1; i < start1 + segment.length*2/3 - 2; i+=2) {
					_indices.push(i, i+1, i+3, i, i+3, i+2);
					_reverseIndices.push(i+3, i+1, i, i+2, i+3, i);
				} 
				
				transform2.copyToMatrix3D(transform);
			}
			
			if (join) {
				for (i = 0; i < links.length-1; i++) {
					_indices.push(start1+links[i]*2+1, i, i+1, start1+links[i]*2+1, i+1, start1+links[i+1]*2+1);
					_reverseIndices.push(i+1, i, start1+links[i]*2+1, start1+links[i+1]*2+1, i+1, start1+links[i]*2+1);
				}
			}
		}
		
		
		protected function onContext3DCreate(event:Event):void {				
			_context3D = Stage3D(event.currentTarget).context3D;

			if (!_context3D)
				return;

			_context3D.enableErrorChecking = true;
			initialiseData();
		}
		
		
		protected function distanceToCentre(rotate:Number, segmentWidth:Number, dividerWidth:Number):Number {
			var d:Number = segmentWidth + dividerWidth*Math.cos(rotate/180*Math.PI);
			return (d/2)/Math.tan(rotate/180*Math.PI);
		}
		
		
		protected function toColourVector(value:uint):Vector3D {
			return new Vector3D((value>>16 & 0xff)/256, (value>>8 & 0xff)/256, (value & 0xff)/256, 1.0);
		}
		
		
		protected function initialiseData():void {			
			
			// Back Buffer
			_context3D.configureBackBuffer(stage.stageWidth, stage.stageHeight, 2, false);
			_context3D.setCulling(Context3DTriangleFace.FRONT);
			
			// Upload Indices
			_indexBuffer = _context3D.createIndexBuffer( _indices.length );
			_indexBuffer.uploadFromVector(_indices, 0, _indices.length );
			
			// Upload Indexes Of Room Reflection In Floor
			_reverseIndexBuffer = _context3D.createIndexBuffer( _reverseIndices.length );
			_reverseIndexBuffer.uploadFromVector(_reverseIndices, 0, _reverseIndices.length );
			
			// Upload vertex data
			_vertexBuffer = _context3D.createVertexBuffer( _vertices.length/N, N); 
			_vertexBuffer.uploadFromVector(_vertices, 0, _vertices.length/N);
			
			var assembler:AGALMiniAssembler = new AGALMiniAssembler();
			
			assembler.assemble( Context3DProgramType.VERTEX,
				"m44 vt0, va0, vc0 \n" +		// vertex transformation
				"mov op, vt0 \n" +				// output transformed vertex
				"mov v1, va1 \n" +				// v1 = uv
				"mov v0, va2 \n" +				// v0 = rgb
				
				// Z-fog
				"div vt0.z vt0.z vc4.z \n" +	// divide distance from camera by vc4.z
				"sub v2, vc4.w, vt0.z \n"		// v2 = 1 - scaled distance
			);
			_vertexShaderAssembler = assembler.agalcode;
			
			assembler.assemble( Context3DProgramType.VERTEX,
				"m44 vt0, va0, vc0 \n" +		// vertex transformation
				"mov op, vt0 \n" +				// output transformed vertex
				"mov v1, va1 \n" +				// v1 = uv
				"mov v0, va2 \n" +				// v0 = rgb
				
				// Z-fog
				"div vt0.z, vt0.z, vc4.z \n" +	// divide distance from camera by vc4.z
				"sub vt0.z, vc4.w, vt0.z \n" +	// 1 - scaled distance
				
				// Y-fog
				"div vt0.y, vt0.y, vc4.y \n" +	// divide distance from camera by vc4.z
				"add vt0.y, vc4.x, vt0.y \n" +	// vc4.x + scaled distance (because y distance is -ve)
				
				"mul v2, vt0.y, vt0.z"			// multiply Z and Y fog
			);
			_reflectionShaderAssembler = assembler.agalcode;
			
			// A simple fragment shader which will use the vertex position as a colour
			
			assembler.assemble( Context3DProgramType.FRAGMENT,
				"sub ft0.x, fc0.w, v1.z \n" +
				"sub ft0.y, fc0.w, v1.y \n" +
				
				"min ft0.z, ft0.x, ft0.y \n" +
				"min ft0.z, ft0.z, v1.z \n" +
				"min ft0.z, ft0.z, v1.y \n" +	// minimum distance from corner of uv rect

				"mul ft1.xyz, fc0.xyz, ft0.zzz \n" +	// multiply by detail colour
				"add ft1.xyz, v0, ft1.xyz \n" +		// add ambient colour
				"sat ft0.xyz, v2 \n" +
				"mul ft0, ft1.xyz, ft0.xyz \n" +		// multiply by fog
				"tex ft1, v1.xy, fs0 <2d,repeat,nomip>\n" +
				"mul oc, ft1, ft0 \n"					// output colour
			);
			
			
			_fragmentShaderAssembler = assembler.agalcode;			
			
			_shaderProgram = _context3D.createProgram();

			_projectionMatrix.perspectiveFieldOfViewLH(60.0*Math.PI/180,WIDTH/HEIGHT,0.1,1000.0);
			
			_modelMatrix.identity();
			_modelMatrix.appendTranslation(-0.3, -0.4, 0.6);
			_modelMatrix.appendRotation(10, Vector3D.Y_AXIS);
			_rotationMatrix = _modelMatrix.clone();

			var bitmap:Bitmap = new PANEL();
			_texture = _context3D.createTexture(bitmap.bitmapData.width, bitmap.bitmapData.height, Context3DTextureFormat.BGRA, false);
			_texture.uploadFromBitmapData(bitmap.bitmapData);
			_context3D.setTextureAt(0, _texture);
			
			// start animating
			addEventListener(Event.ENTER_FRAME,enterFrame);
		}

		
		protected function enterFrame(event:Event):void {
			const rotate:Number = -28*0.04;
			_context3D.clear();
			_context3D.setVertexBufferAt( 0, _vertexBuffer,  0, Context3DVertexBufferFormat.FLOAT_3 );  //va0  xyz
			_context3D.setVertexBufferAt( 1, _vertexBuffer,  3, Context3DVertexBufferFormat.FLOAT_3 );  //va1  uvw
			_context3D.setVertexBufferAt( 2, _vertexBuffer,  6, Context3DVertexBufferFormat.FLOAT_3 );  //va1  rgb
			
			_context3D.setProgram ( _shaderProgram );
			

			_modelMatrix.appendRotation(rotate, Vector3D.Y_AXIS);

			
			_modelMatrix.copyToMatrix3D(_finalMatrix);
			_finalMatrix.append(_projectionMatrix);
			_t+=1;
			
			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _finalMatrix, true);		// va0 - va3  Transformation Matrix
			_context3D.setProgramConstantsFromVector(Context3DProgramType.VERTEX, 4, Vector.<Number>([ 0.8, 4.0, 5.0, 1.0 ]));	// va4  0.0, y-fog, z-fog, 1.0
			_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 0, Vector.<Number>([ 1.0, 1.0, 1.0, 1.0 ]) );	// fc0  detail colour

		//	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 2, Vector.<Number>([ 0.8, 0.8, 0.8, 1.0 ]) ); // fc1  ambient colour
			
			_shaderProgram.upload( _vertexShaderAssembler, _fragmentShaderAssembler);
			_context3D.drawTriangles( _indexBuffer);

			_modelMatrix.copyToMatrix3D(_finalMatrix);
			_finalMatrix.appendScale(1.0, -1.0, 1.0);
			_finalMatrix.appendTranslation(0.0, -0.8, 0.0);		// invert and move mesh to form reflection
			_finalMatrix.append(_projectionMatrix);
			_context3D.setProgramConstantsFromMatrix(Context3DProgramType.VERTEX, 0, _finalMatrix, true);
		//	_context3D.setProgramConstantsFromVector(Context3DProgramType.FRAGMENT, 1, Vector.<Number>([ 0.1, 0.1, 0.1, 1.0 ]) ); // fc1  ambient colour
			_shaderProgram.upload( _reflectionShaderAssembler, _fragmentShaderAssembler);
			_context3D.drawTriangles( _reverseIndexBuffer);
			
			_context3D.present();
		}
		
	}
}